Conversation
886f6ca to
80cbb8d
Compare
6d08274 to
b6eabf6
Compare
|
Interesting idea. Two unpolished questions:
|
|
Thank you for looking into it! The idea of introducing stream hooks allowing user space to implement a scheduler originates from a suggestion of Frode Børli a long time ago. I'm working on this as part of STF.
In the current iteration, The plan is to change this logic a bit, to make the system compatible with EdgeTriggered mode, which allows to register streams only once:
This allows userspace to register streams only once. Systems without EdgeTriggered can emulate it. Handling concurrent accesses to the same stream (which leads to multiple fibers waiting for the same stream) is on my TODO, but I'm not sure how this should be handled yet. What's almost certain is that these accesses will needed to wait in higher layers, not in
Exactly. Right now the plan is to introduce an Edit: All of these have been implemented now. |
Introduce function stream_set_hook(callable $hook). The given hook is called just before performing a read or write operation on any stream, and must have the following signature: function (/*resource*/ $fd, StreamOperation $operation).
Closing a stream resource frees the stream itself, so doing that in a hook will result in UAFs in some parent function. Possible solutions: * Deny fclose() during hook invokation * Never access streams after stream operations, or use the resource to check whether the stream was closed * Do not free the php_stream itself in fclose(). Replace ->ops with always-fail handlers, mark as eof.
The stream hook is now a php_pollfd_for() replacement. Stream ops typically call php_pollfd_for() before any blocking operations, to implement timeouts. We can hook here to delegate polling to user-space. TODO: * php_pollfd_for() is not called where there is no timeout. Ensure that we call it when a hook is installed. * Prevent concurrent stream ops (lock / serialize) * Timeouts should be handled by the hook
Rationale: * We can't ensure consistency of internal structures when a stream is accessed concurrency, at least for the same direction. * It may be possible to accept concurrent accesses from different directions (read and write) * Valid use-cases for concurrent accesses in the same direction are unknown. This would require synchronization from the concumer.
* Introduce StreamPollWeakHandle, which holds its stream weakly * Context stops watching any Handle whose stream is collected * Introduce Context::onWatcherRemoved(?callable $callback). Callback is invoked when a weak handle is removed automatically. * Generalized to Curl's SocketHandle -> SocketWeakHandle
* Mark OS sockets as non-blocking when creating a stream (without affecting the stream's blocking status) * Perform I/O optimistically before polling * Poll on EAGAIN only This should be faster (less polling), and makes operations compatible with edge-triggering.
Introduce function
Io\Hooks\set_hooks(Io\Hooks\Hooks $hooks). Methods on the given object are used as a replacement forphp_pollfd_for(), and more.Internally
php_pollfd_for()is called before any stream operation that may block, to implement timeouts. Therefore it's a perfect place to context switch.Example: https://github.com/arnaud-lb/php-src/blob/io-hooks/ext/standard/tests/streams/hooks/use-case.phpt
TODO:
php_pollfd_for()is called when there is no timeoutCurrent API: https://github.com/arnaud-lb/php-src/blob/io-hooks/ext/standard/io_hooks.stub.php